#include "font.h"

#include "graphics/graphics_util.h"

#include "math/vec2.h"

#include "lang/lang.h"
#include "lang/rules.h"

#include "util/common_macros.h"		// macros
#include "util/log.h"		// macros
#include "util/mmgr.h"		// Needs to come in early

#include <math.h>
#include <cassert>

#define	FONT_BASE_X		800.0f
#define FONT_BASE_Y		600.0f

Font::Font( const std::string& Name ) :
	m_Name (Name),
	m_AddBorder(false),
	m_TexWidth(1),
	m_TexHeight(1),
	m_AddLine(false),
	m_ColorMask(1.0f, 1.0f, 1.0f, 1.0f),
	m_Height(0), 
	m_Width(0),
	m_Italic(false),
	m_Weight(0)

{
	m_Highlight = ColorRGBA(255, 255, 0, 255);
	m_LineSpacing = 0;

	SET_OVERRUN(m_StartGuard);
	SET_OVERRUN(m_EndGuard);
}

Font::~Font()
{
	int	Test = 0; 	
	m_GlyphMap.ClearSet();
}

bool Font::Init(   	const std::string& _Face,
                    unsigned long      _Width,
                    unsigned long      _Height,
					bool				_Italic, 
					unsigned int		_Weight)
{
	m_Height = _Height; 
	m_Width = _Width;
	m_Italic = _Italic;
	m_Face = _Face;
	m_Weight = _Weight;
	return CustomInit();

}

void	Font::Flush()
{

}

void	Font::Recreate()
{
	Init(m_Face, m_Width, m_Height, m_Italic, m_Weight);
}

void	Font::Resize(const std::string& _Face, int Width, int Height, int Weight, unsigned long CharSet)
{
	if (	(strcmpi(m_Face.c_str(), _Face.c_str())==0) &&
			Width == m_Width &&
			Height == m_Height &&
			Weight == m_Weight	)
	{
		// already the same
		return;
	}
	Init(_Face, Width, Height, m_Italic, Weight);
}

float Font::GetHeight()
{
	float Height = (float)GetGraphics()->GetScreenHeight();
	return m_Height / Height;
}

bool  Font::IsEscapeChar(unsigned short ID)
{
	return ( ID == FORCE_LB) || 
		( ID == FORCE_TAB) || 
//		( ID == FORCE_WS) ||  // deprecated flag
		( ID == FORCE_BOLD) ||
		( ID == FORCE_NBSP);
}

Font::GlyphData* Font::GetGlyphIndex(unsigned short Glyph)
{
	return m_GlyphMap.GetElement(Glyph);
}


void	Font::BuildGlyphData(const std::wstring& Text, int	Width, int Height, LineData* LineA, GlyphData**	GlyphA, int& Lines, int& Glyphs, bool Draw)
{
	int	StrLen = (int)Text.size();
	if (StrLen == 0) return;
	assert(StrLen < MAX_CHAR);
	for (int i=0; i<StrLen; i++)
	{
		unsigned short	Glyph = Text[i];
		GlyphA[i] = GetGlyphIndex(Glyph);	
		//assert(GlyphA[i] && "Exceeds max valid glyphs / frame");
	}
	// need to handle more than 1024 characters
	// need to handle more than N glyphs in a single frame...

	// if there's any new ones, cache them
	CacheGlyphs(true);

	int				LastBreak = 0;
	int				LastBreakPos = 0;
	float			LineStart = 0;
	float			CurrentX = 0;
	float			CurrentY = 0;
	int				LineI = 0;
	LineData*		pLine = &LineA[LineI++];
	GlyphData*		pWhiteSpc = GetGlyphIndex(L' ');
	pLine->Reset();
	for (int i=0; i<StrLen; i++)
	{
		unsigned short KeyCode  = Text[i];
		if (!GlyphA[i]) 
		{
			// why no valid glyph??
			if (!IsEscapeChar(KeyCode)) continue; 
		} else
		{
			KeyCode  = GlyphA[i]->Glyph;
		}
		if (IsEscapeChar(KeyCode))
		{
			// control codes
			switch (KeyCode) 
			{
				case 		FORCE_LB:	
					pLine->To =	i;
					pLine->Width = CurrentX;
					assert(pLine->To <= StrLen);
					pLine = &LineA[LineI++];
					assert(LineI < MAX_LINES);
					pLine->Reset();
					pLine->From = i+1;
					CurrentX  = 0;
					LineStart = float(i+1);
					LastBreak = i+1;
					LastBreakPos = 0;
					break;
				case 		FORCE_TAB:			
					CurrentX = ( (CurrentX / TAB_WIDTH) + 1) * TAB_WIDTH; 
					break;
//				case		FORCE_WS:	// deprecated flag
//					LastBreak = i; 
//					LastBreakPos =(int) CurrentX;
//					break;
				case		FORCE_BOLD:
					// does nothing in this pass
					break;
				case		FORCE_NBSP:
					if (pWhiteSpc)
						CurrentX += pWhiteSpc->Width;
					break;
			}
		} else
		{
			GlyphData* pGlyph = GlyphA[i];
			if (pGlyph->Glyph == L' ')	
			{
				LastBreak = i;
				LastBreakPos = (int)CurrentX;
			}

			if (pGlyph->Width > Width)
			{
				// Damn glyph's too large for the space!
				pLine->To = 0;
				pLine->Width = 0;
				Glyphs	= 0;		// doesn't work when we line-break
				Lines	= 0;
				return;
			}

			if (CurrentX + pGlyph->Width + pGlyph->OffsetX + pGlyph->PreOffset> Width)
			{
				bool	LineBreak = false;
				// find the 
				if (LineStart==LastBreak)	// bloody long word
				{
					if (LastBreakPos==0)
					{
						LastBreakPos = int(CurrentX - (pGlyph->Width + pGlyph->OffsetX + pGlyph->PreOffset));
					} else
					{
						LastBreakPos = (int)CurrentX;
					}
					LastBreak = i-1;
					LineBreak = true;
				}

				pLine->To =	LastBreak;
				pLine->Width = (float)LastBreakPos;
				int result = lang::Rules::Instance().ApplyRules(Text, pLine->From, LastBreak);
				if (result != LastBreak)
				{
					pLine->To = LastBreak = result;
					//RecalcLineWidth(Text, GlyphA, pLine);
				}
				else
				{
					pLine->To =	LastBreak;
					//pLine->Width = LastBreakPos;
				}
				RecalcLineWidth(Text, GlyphA, pLine); // ADRIAN: always do this... something wrong with 1st line's LastBreakPos calculation (that I can't spot)
				
				assert(pLine->To <= StrLen);
				pLine = &LineA[LineI++];
				if (LineI >= MAX_LINES) return;
				assert(LineI < MAX_LINES);
				pLine->Reset();
				CurrentX  = 0;
				if (LineBreak)
				{
					pLine->From = LastBreak;
					i = LastBreak = LastBreak;
					LineStart = (float)i;
				} else
				{
					pLine->From = LastBreak+1;
					i = LastBreak = LastBreak + 1;
					LineStart = (float)i+1;
				}

			} else
			{
				CurrentX += pGlyph->Width + pGlyph->OffsetX + pGlyph->PreOffset;
			}
		}
	}
	pLine->To = StrLen;
	assert(pLine->To <= StrLen);
	//pLine->Width = CurrentX;
	RecalcLineWidth(Text, GlyphA, pLine); // ADRIAN: always do this... something wrong with last line's CurrentX calculation (that I can't spot)
	Glyphs	= StrLen;		// doesn't work when we line-break
	Lines	= LineI;
}

void	Font::RecalcLineWidth(const std::wstring& Text, GlyphData** GlyphA, LineData* pLine)
{
	assert( (pLine->From <= (int)Text.size()) && (pLine->To <= (int)Text.size()) );
	GlyphData* pWhiteSpc = GetGlyphIndex(L' ');
	pLine->Width = 0.0f;

	for (int idx = pLine->From; idx < pLine->To; idx++)
	{
		unsigned short KeyCode  = Text[idx];
		if (IsEscapeChar(KeyCode))
		{
			switch (KeyCode)
			{
			case FORCE_TAB:			
				pLine->Width = ( (pLine->Width / TAB_WIDTH) + 1) * TAB_WIDTH; 
				break;
			case FORCE_NBSP:
				if (pWhiteSpc)
					pLine->Width += pWhiteSpc->Width;
				break;
			}
		}
		else if (GlyphData* pGlyph = GlyphA[idx])
		{
			pLine->Width += pGlyph->Width + pGlyph->OffsetX + pGlyph->PreOffset;
		}
	}
}

void	Font::GetTextExtent(	const std::string& Text,const math::Vec2& Box,math::Vec2& From,math::Vec2& Size,unsigned int Flags, int* pLineC)
{
	GetTextExtent(lang::Lang::ConvertAtoW(Text), Box, From, Size, Flags, pLineC);
}

void	Font::GetTextExtent(	const std::wstring& Text, const math::Vec2& Box,math::Vec2& From, math::Vec2& Size, unsigned int Flags, int* pLineC)
{
	GlyphData*			GlyphA[MAX_CHAR];
	LineData			LineA[MAX_LINES];
	int							LineC, GlyphC;
	if (Text.empty()) return;

	Graphics*		pGraph = GetGraphics();
	float			ScreenW = pGraph->GetScreenWidth();
	float			ScreenH = pGraph->GetScreenHeight();
	float			BoxW = (Box.x * ScreenW);
	float			BoxH = (Box.y* ScreenH);
	if (BoxW <= 1) BoxW = ScreenW;
	if (BoxH <= 1) BoxH = ScreenH;
	BuildGlyphData(Text, (int)BoxW, (int)BoxH, LineA, GlyphA, LineC, GlyphC, true);
	RenderGlyphData(From, Box, 0xFFFFFFFF, LineA, GlyphA, LineC, GlyphC, Flags, false);
	From.x = m_MinExtent.x / float(ScreenW);
	From.y = m_MinExtent.y / float(ScreenH);
	Size.x = (m_MaxExtent.x-m_MinExtent.x) / float(ScreenW);
	Size.y = (m_MaxExtent.y-m_MinExtent.y) / float(ScreenH);

	if (pLineC) *pLineC = LineC;
}

bool	Font::GetCharOffsets(unsigned short ch, int* pOffsetX, int* pPreOffset)
{
	assert(pOffsetX || pPreOffset);
	if (GlyphData* pGlyph = GetGlyphIndex(ch))
	{
		if (pOffsetX)	*pOffsetX = pGlyph->OffsetX;
		if (pPreOffset)	*pPreOffset = pGlyph->PreOffset;
		return true;
	}
	return false;
}


void	Font::Render(	const std::string& Text, const math::Vec2& Pos, const math::Vec2& Size, unsigned int Flags,	COLOR		Color , 	bool		Clip)
{
	Render(lang::Lang::ConvertAtoW(Text), Pos, Size, Flags, Color, Clip);
}

void	Font::RenderCentered(	const std::string& Text,
							const math::Vec2& Pos,
							const math::Vec2& Size,
							unsigned int Flags,
							COLOR		Color,
							bool	Clip )
{
	RenderCentered(lang::Lang::ConvertAtoW(Text), Pos, Size, Flags, Color, Clip);
}

void	Font::RenderCentered(	const std::wstring& Text,
							const math::Vec2& Pos,
							const math::Vec2& Size ,
							unsigned int Flags , 
							COLOR		Color ,
							bool	Clip )
{
	math::Vec2	TxtFrom, TxtSize;
	GetTextExtent(Text, math::Vec2(1,1), TxtFrom, TxtSize);
	Render(Text, Pos - (TxtSize*0.5f), Size, Flags, Color, Clip);
}



void	Font::RenderGlyphData(const math::Vec2& Pos, const math::Vec2& Size, COLOR Color, LineData* LineA, GlyphData**	GlyphA, int Lines, int Glyphs, unsigned int Flag, bool Draw)
{
	/* Adrian: deprecated
	if (m_AddBorder)
		Color = 0xFFFFFFFF;	// bordered fonts are ALWAYS white.
	*/


	// for now, just do a pure render in the top right
	Graphics::Vertex2D*	VertA = GetGraphics()->GetTempVert();
	COLOR		BaseColor = Color;
	COLOR		BoldColor = m_Highlight;
	COLOR		CurrentColor = BaseColor;
	bool			IsBold = false;	
	Graphics*		pGraph = GetGraphics();
	float			ScreenW = (float)pGraph->GetScreenWidth();
	float			ScreenH = (float)pGraph->GetScreenHeight();
	float			StartX = (Pos.x * ScreenW) ;
	float			StartY = (Pos.y * ScreenH) ;
	float			AreaX  = (Size.x * ScreenW) ;
	float			AreaY  = (Size.y * ScreenH) ;
	float			EndX   = (Pos.x + Size.x) * ScreenW;
	float			EndY   = (Pos.y + Size.y) * ScreenH;
	float			CurrentX = 0;
	float			CurrentY = 0;
	float			LineHeight = m_Height + (float)m_LineSpacing;
	int				VertI	= 0;
	int				RenderC=0;

	GlyphData*		pWhiteSpc = GetGlyphIndex(L' ');

	m_MinExtent		= math::Vec2(1000000,1000000);
	m_MaxExtent		= -m_MinExtent;

	float			TotalHeight = (float)(LineHeight * Lines);
	if (Flag & FONT_BOTTOM)	StartY = (Pos.y* ScreenH) + (AreaY - TotalHeight); else
	if (Flag & FONT_VCENTER)	StartY = (Pos.y* ScreenH) + ((AreaY - TotalHeight) * 0.5f); 
	// set texture
	if (Draw)
	{
		SetTexture();

	}
	for (int LineI=0; LineI<Lines; LineI++)
	{
		LineData*	pLine = &LineA[LineI];

		CurrentY = StartY + (LineI * LineHeight);
		CurrentX = StartX;	// left align
		if (Flag & FONT_CENTER)	CurrentX = StartX + ((AreaX - pLine->Width) * 0.5f); else	// center it
		if (Flag & FONT_RIGHT)	CurrentX = StartX + (AreaX - pLine->Width);				// right align it

		pLine->x = CurrentX;
		pLine->y = CurrentY;
		for (int i=pLine->From; i<pLine->To; i++)
		{
			if (!GlyphA[i]) continue;
			unsigned short KeyCode  = GlyphA[i]->Glyph;
			if (IsEscapeChar(KeyCode))
			{
				// control codes
				switch (KeyCode) 
				{
					case 		FORCE_TAB:			
						CurrentX = ( (CurrentX / TAB_WIDTH) + 1) * TAB_WIDTH; 
						break;
					case		FORCE_BOLD:
						IsBold = !IsBold;
						CurrentColor = (IsBold) ? BoldColor : BaseColor;
						break;
					case		FORCE_NBSP:
						if (pWhiteSpc)
							CurrentX += pWhiteSpc->Width;
						break;
				}
			} else
			{
				assert(i >= 0 && i <= Glyphs);
				GlyphData* pGlyph = GlyphA[i];

				float	x = floorf(CurrentX + pGlyph->PreOffset);
				float	y = floorf(CurrentY);
				
				float	w = floorf((float)pGlyph->Width);
				float	h = floorf((float)pGlyph->Height);
				if (Draw)
				{
					float	tx1 = pGlyph->Pos.x;
					float	ty1 = pGlyph->Pos.y;
					float	tx2 = pGlyph->Pos.x+ (w/float(m_TexWidth)); //pGlyph->Size.x;
					float	ty2 = pGlyph->Pos.y+ (h/float(m_TexHeight)); //pGlyph->Size.y;

					COLOR	col = ColorRGBA( (unsigned char)(float(ColorGetR(CurrentColor)) * m_ColorMask.x),
											 (unsigned char)(float(ColorGetG(CurrentColor)) * m_ColorMask.y),
											 (unsigned char)(float(ColorGetB(CurrentColor)) * m_ColorMask.z),
											 (unsigned char)(float(ColorGetA(CurrentColor)) * m_ColorMask.w) );

					VertA[VertI++].Fill(x+0+0.5f,y+h+0.5f, col, tx1, ty2 );
					VertA[VertI++].Fill(x+0+0.5f,y+0+0.5f, col, tx1, ty1 );
					VertA[VertI++].Fill(x+w+0.5f,y+h+0.5f, col, tx2, ty2 );

					VertA[VertI++].Fill(x+w+0.5f,y+0+0.5f, col, tx2, ty1 );
					VertA[VertI++].Fill(x+w+0.5f,y+h+0.5f, col, tx2, ty2 );
					VertA[VertI++].Fill(x+0+0.5f,y+0+0.5f, col, tx1, ty1 );
				}
				m_MinExtent.x = UTIL_LOW(m_MinExtent.x, x);
				m_MinExtent.y = UTIL_LOW(m_MinExtent.y, y);
				m_MaxExtent.x = UTIL_HIGH(m_MaxExtent.x, x+w);
				m_MaxExtent.y = UTIL_HIGH(m_MaxExtent.y, y+h);

				CurrentX += w + pGlyph->OffsetX;
				RenderC++;
			}
		}
	}
	if (VertI > 0 && Draw)	GetGraphics()->DrawVertex2D(VertA, VertI);

	if (RenderC==0)
	{
		m_MinExtent		= m_MaxExtent	 = math::Vec2(0,0);
	}
	if (Draw)
	{
	}

	if (Draw && m_AddLine)
	{
		VertI = 0;
		for (int LineI=0; LineI<Lines; LineI++)
		{
			LineData*	pLine = &LineA[LineI];
			math::Vec2	From, To;
			From.x = pLine->x / (float)ScreenW;
			From.y = (pLine->y+(m_Height*0.75f)) /  (float)ScreenH;
			To.x = pLine->Width /  (float)ScreenW;
			To.y = 1/600.0f;
			GraphicsUtil::FillQuad(From, To, math::Vec2(0,0), math::Vec2(1,1), ColorRGBA(0, 0, 0, 255), &VertA[VertI]);
			VertI+=6;
		}
		if (VertI > 0)	GetGraphics()->DrawVertex2D(VertA, VertI);
	}

}
